package com.katze.boot.file.servlet;

import com.katze.boot.file.entity.FileTransfer;
import com.katze.boot.file.registry.FileSystemRegistrationBean;
import com.katze.boot.file.service.FileSystemService;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Optional;
import java.util.function.Consumer;

/**
 * 文件上传服务类
 * @步骤1：断点校验（若目标目录不存在则创建）
 * @步骤2：上传文件块（若目标文件块存在则覆盖）
 * @步骤3：若干个文件块合并成一个完整文件
 * @说明：bean扩展上传文件入口
 * @param <S> 文件系统服务类
 */
public abstract class FileUploadService<S extends FileSystemService> implements IFileUploadService {
    private static final Logger log = LoggerFactory.getLogger(FileUploadService.class);

    @Resource
    protected S fileSystemService;

    @Resource
    private FileSystemRegistrationBean bean;

    public FileUploadService() {}

    public FileUploadService (ServletRegistrationBean<DefaultFileTransferServlet> servletRegistrationBean) {
        try {
            Component component = getClass().getAnnotation(Component.class);
            if (component != null) {
                servletRegistrationBean.getServlet().add(StringUtils.isBlank(component.value())? getClass().getSimpleName():component.value(), this);
            } else if (getClass().getSimpleName().startsWith("Default")) {
                servletRegistrationBean.getServlet().add("default", this);
            } else {
                // 需要在实现FileSystemService接口类上加带有name方法的注解
                Type type = getClass().getGenericSuperclass();
                if (type instanceof ParameterizedType) {
                    Class<?> c = (Class<?>) ((ParameterizedType) type).getActualTypeArguments()[0];
                    Annotation annotation = c.getAnnotations()[0];
//                    Object value= AnnotationUtils.getValue(c.getAnnotation(annotationClass), "name");
                    Method method = annotation.annotationType().getDeclaredMethod("name");
                    servletRegistrationBean.getServlet().add((String) method.invoke(annotation), this);
                }
            }
        } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
            throw new IllegalStateException(e);
        }
    }

    @Override
    public String[] beforeUpload(FileTransfer transfer) {
        if (transfer.getIndex() != -1) {
            File tempDir = new File(transfer.getTemp(), transfer.getOwner() + "_" + transfer.getId());
            if (!tempDir.exists() && !tempDir.mkdirs()) {
                log.error("{}目录创建失败", tempDir);
                throw new IllegalStateException("当前上传功能暂停使用");
            }
            return tempDir.list((dir, name) -> StringUtils.isNumeric(name));
        } else {
            return new String[]{};
        }
    }

    @Override
    public void upload(FileTransfer transfer) throws IOException {
        if (transfer.getIndex() == -1) {
            try {
                File target = this.save(transfer, file -> {
                    try (FileOutputStream out = new FileOutputStream(file, false)){
                        out.write(transfer.getData());
                    } catch (Exception e) {
                        log.error("{}文件保存失败", file, e);
                        throw new IllegalStateException("文件保存失败");
                    }
                });
                this.apply(transfer.temp(target));
            } catch (Exception e) {
                log.error("文件保存失败", e);
                throw new IllegalStateException("文件保存失败");
            }
        } else {
            Path dest = transfer.getTemp().toPath().resolve( (transfer.getOwner() + "_" + transfer.getId()) + "/" + transfer.getIndex());
            if (Files.exists(dest)) {
                log.warn("{}文件将被覆盖", dest);
            }
            Files.write(dest, transfer.getData(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
        }
    }

    @Override
    public void afterUpload (FileTransfer transfer) {
        File dir = new File(transfer.getTemp(), transfer.getOwner() + "_" + transfer.getId());
        Optional<File[]> optional = Optional.ofNullable(dir.listFiles(file -> StringUtils.isNumeric(file.getName())));
        if (optional.isPresent()) {
            try {
                File target = this.save(transfer, file -> {
                    try (FileOutputStream fileOutputStream = new FileOutputStream(file, false); FileChannel outChannel = fileOutputStream.getChannel()){
                        optional.map(Arrays::stream)
                                .get()
                                .sorted(Comparator.comparing(o -> Integer.valueOf(o.getName())))
                                .forEach(chunk -> {
                                    try (FileInputStream fileInputStream = new FileInputStream(chunk); FileChannel inChannel = fileInputStream.getChannel()){
                                        inChannel.transferTo(0, inChannel.size(), outChannel);
                                    } catch (IOException e) {
                                        log.error("文件块目录:{}, 实际文件名:{}, 文件序号:{}合并失败", dir, transfer.getName(), chunk.getName());
                                        throw new IllegalStateException("文件合并失败");
                                    }
                                    if (!chunk.delete()) {
                                        log.warn(chunk.getAbsolutePath() + "文件删除失败");
                                    }
                                });
                    } catch (Exception e) {
                        log.error("文件块目录:{}, 输出文件:{},合并失败", dir, file, e);
                        throw new IllegalStateException("文件合并失败");
                    }
                });
                this.apply(transfer.temp(target));
            } catch (Exception e) {
                log.error("文件保存失败", e);
                throw new IllegalStateException("文件保存失败");
            } finally {
                String[] fs = dir.list();
                if (fs != null && fs.length == 0 && !dir.delete()) log.warn("{}目录删除失败", dir);
            }
        } else {
            throw new IllegalStateException("文件已超时重新上传");
        }
    }

    protected void apply(FileTransfer transfer) {
        bean.get(transfer.getFrom()).datafile(transfer);
    }

    public abstract File save(FileTransfer transfer, Consumer<File> save) throws Exception;
}
